package org.meb.lotrlcg.scan;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;
import org.meb.lotrlcg.db.model.CardType;
import org.meb.lotrlcg.scan.model.Card;
import org.meb.lotrlcg.scan.model.CardProperty;
import org.meb.lotrlcg.scan.model.CardSetInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CardSiteParser {

	private static final Logger log = LoggerFactory.getLogger(CardSiteParser.class);

	private static final Map<String, String> mappings;

	static {
		mappings = new HashMap<String, String>();
		mappings.put("attack", "attack");
		mappings.put("cost", "cost");
		mappings.put("defense", "defense");
		mappings.put("encounter info", "encounterInfo");
		mappings.put("encounter set", "encounterSet");
		mappings.put("hit points", "hitPoints");
		mappings.put("illustrator", "illustrator");
		mappings.put("number", "number");
		mappings.put("quantity", "quantity");
		mappings.put("quest points", "questPoints");
		mappings.put("sphere", "sphere");
		mappings.put("set", "setSymbol");
		mappings.put("starting threat", "startingThreat");
		mappings.put("threat", "threat");
		mappings.put("threat threashold", "engageThreat");
		mappings.put("willpower", "willpower");
	}

	public void parseSites(List<CardSetInfo> csInfos, CardHandler handler) throws IOException {
		for (CardSetInfo csInfo : csInfos) {
			parseSite(csInfo, handler);
		}
	}

	public void parseSite(CardSetInfo csInfo, CardHandler handler) throws IOException {
		List<Document> docs = new ArrayList<Document>();
		log.info("parseSite(): base doc: {}", csInfo.getUrl());
		docs.add(Jsoup.connect(csInfo.getUrl()).timeout(20000).get());
		Elements aElems = docs.get(0).select("div.topic_controls li.page a");
		Iterator<Element> iter = aElems.iterator();
		while (iter.hasNext()) {
			String nextUrl = iter.next().attr("href");
			log.info("next doc: {}", nextUrl);
			docs.add(Jsoup.connect(nextUrl).timeout(20000).get());
		}

		int total = 0;
		for (int i = 0; i < docs.size(); i++) {
			List<Card> cards = parseCards(docs.get(i));
			log.info("parseSite(): site cards count: {}", cards.size());
			total += cards.size();
			for (Card card : cards) {
				card.setSetName(csInfo.getName());
				handler.handle(card);
			}
		}
		log.info("parseSite(): set cards total: {}", total);
	}

	private List<Card> parseCards(Document siteDoc) {
		List<Card> cards = new ArrayList<Card>();

		Elements tdCards = siteDoc.select("div.ipsBox table.ipb_table > tbody > tr > td");
		Iterator<Element> iter = tdCards.iterator();
		while (iter.hasNext()) {
			Element tdCard = iter.next();
			Element tdCardInfo = tdCard.select("tr:eq(0) > td").get(1);
			Card card = parseCard(tdCardInfo);
			if (card != null) {
				cards.add(card);
			}
		}

		return cards;
	}

	private Card parseCard(Element tdCardInfo) {
		String name = tdCardInfo.child(0).text().trim();
		String typeString = tdCardInfo.child(2).nextSibling().toString().trim();
		CardType type = CardType.valueOf(typeString.toUpperCase());

		log.debug("parseCard(): type: {}, name: {}", type, name);

		Card card = new Card(type);
		switch (type) {
			case ALLY:
				parseAllyCardInfo(card, tdCardInfo);
				break;
			case ATTACHMENT:
				parseAttachmentCardInfo(card, tdCardInfo);
				break;
			case CAMPAIGN:
				parseCampaignCardInfo(card, tdCardInfo);
				break;
			case ENEMY:
				parseEnemyCardInfo(card, tdCardInfo);
				break;
			case EVENT:
				parseEventCardInfo(card, tdCardInfo);
				break;
			case HERO:
				parseHeroCardInfo(card, tdCardInfo);
				break;
			case LOCATION:
				parseLocationCardInfo(card, tdCardInfo);
				break;
			case OBJECTIVE:
				parseObjectiveCardInfo(card, tdCardInfo);
				break;
			case QUEST:
				parseQuestCardInfo(card, tdCardInfo);
				break;
			case TREACHERY:
				parseTreacheryCardInfo(card, tdCardInfo);
				break;
			default:
				card = null;
		}

		log.debug("card: {}", card);

		return card;
	}

	private void parseCommonCardInfo(Card card, Element tdCardInfo) {
		card.setName(tdCardInfo.child(0).text().trim());
		List<CardProperty> properties = new ArrayList<CardProperty>();
		properties.addAll(extractKeyValueInfo(tdCardInfo, mappings));
		properties.addAll(extractTextProperties(tdCardInfo));
		setCardProperties(card, properties);
	}

	private void parseAllyCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseAttachmentCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseEnemyCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseCampaignCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseEventCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseHeroCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseLocationCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseObjectiveCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseQuestCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private void parseTreacheryCardInfo(Card card, Element tdCardInfo) {
		parseCommonCardInfo(card, tdCardInfo);
	}

	private List<CardProperty> extractKeyValueInfo(Element tdCardInfo, Map<String, String> mappings) {
		List<CardProperty> properties = new ArrayList<CardProperty>();
		Elements bInfoNames = tdCardInfo.select("> b");
		Iterator<Element> iter = bInfoNames.iterator();
		while (iter.hasNext()) {
			Element bInfoName = iter.next();
			String infoName = bInfoName.text();
			if (StringUtils.isBlank(infoName)) {
				continue;
			}

			infoName = infoName.trim().toLowerCase();
			infoName = StringUtils.removeEnd(infoName, ":").trim();

			if (mappings.containsKey(infoName)) {
				String infoValue = bInfoName.nextSibling().toString().trim();
				if (StringUtils.isBlank(infoValue)) {
					continue;
				}

				infoValue = StringUtils.removeStart(infoValue, ":").trim();

				String propertyName = mappings.get(infoName);
				properties.add(new CardProperty(propertyName, infoValue));
				log.debug("extractKeyValueInfo(): propertyName: {}, infoName: {}, infoValue: {}",
						new Object[] { propertyName, infoName, infoValue });
			}
		}
		return properties;
	}

	private void setCardProperties(Card card, List<CardProperty> properties) {
		for (CardProperty property : properties) {
			Object value = property.getValue();
			if (((String) value).equals("X")) {
				value = new Integer(-1);
			}

			try {
				String name = property.getName();
				log.debug("setCardProperties(): set property: {} <- {}", name, value);
				BeanUtils.setProperty(card, name, value);
				value = PropertyUtils.getProperty(card, name);
				log.debug("setCardProperties(): get property: {} -> {}", name, value);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private List<CardProperty> extractTextProperties(Element tdCardInfo) {
		String text = "";
		CardProperty storylineProperty = null;

		int brCount = 0;
		Iterator<Element> iter = tdCardInfo.children().iterator();
		while (iter.hasNext()) {
			Element element = iter.next();
			if (element.tagName().equals("br")) {
				brCount++;

				Node sibling = element.nextSibling();
				Element elementSibling = element.nextElementSibling();
				if (sibling == null) {
					continue;
				} else if (sibling == elementSibling) {
					if (elementSibling.tagName().equals("i") && brCount > 0) {
						storylineProperty = extractStorylineProperty(elementSibling);
						break;
					}
				} else {
					String newText = sibling.toString().trim();
					if (StringUtils.isNotBlank(text) && StringUtils.isNotBlank(newText)) {
						text += StringUtils.repeat("\n", brCount);
					}
					text += newText;
					brCount = 0;
				}
			}
		}

		if (StringUtils.isBlank(text)) {
			text = null;
		}

		log.debug("text: {}", text);

		List<CardProperty> properties = new ArrayList<CardProperty>();
		if (StringUtils.isNotBlank(text)) {
			properties.add(new CardProperty("text", StringEscapeUtils.unescapeHtml(text)));
		}
		if (storylineProperty != null) {
			properties.add(storylineProperty);
		}
		CardProperty traitProperty = extractTraitProperty(tdCardInfo);
		if (traitProperty != null) {
			properties.add(traitProperty);
		}
		return properties;
	}

	private CardProperty extractStorylineProperty(Element iElement) {
		String storyline = "";

		int brCount = 0;
		Iterator<Node> iter = iElement.childNodes().iterator();
		while (iter.hasNext()) {
			Node node = iter.next();
			if (node instanceof Element) {
				Element element = (Element) node;
				if (element.tagName().equals("br")) {
					brCount++;
				}
			} else {
				String newText = node.toString().trim();
				if (StringUtils.isNotBlank(storyline) && StringUtils.isNotBlank(newText)) {
					storyline += StringUtils.repeat("\n", brCount);
				}
				storyline += newText;
				brCount = 0;
			}
		}

		log.debug("storyline: {}", storyline);
		CardProperty property = null;
		if (StringUtils.isNotBlank(storyline)) {
			property = new CardProperty("storyline", StringEscapeUtils.unescapeHtml(storyline));
		}
		return property;
	}

	private CardProperty extractTraitProperty(Element tdCardInfo) {
		String trait = null;

		Iterator<Element> iter = tdCardInfo.children().iterator();
		while (iter.hasNext()) {
			Element element = iter.next();
			if (element.tagName().equals("b")) {
				Node sibling = element.nextSibling();
				Element elementSibling = element.nextElementSibling();
				if (sibling == null) {
					continue;
				} else if (sibling == elementSibling || StringUtils.isBlank(sibling.toString())) {
					trait = element.text().trim();
					break;
				}
			}
		}

		log.debug("trait: {}", trait);
		CardProperty property = null;
		if (StringUtils.isNotBlank(trait)) {
			property = new CardProperty("trait", StringEscapeUtils.unescapeHtml(trait));
		}
		return property;
	}

}
